一、指针基本概念
1、指针自身的类型
从语法的角度看,只要把指针声明语句里的指针变量名去掉,剩下的部分就是该指针的类型。
1 | int *ptr; //指针的类型是int* |
2、指针指向的类型
当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。从语法上看,只要把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的部分就是指针所指向的类型。
1 | int *ptr; //指针所指向的类型是int |
*p[N], (*P)[N],及**p的区别:
int *p[N]
表示定义一个指针数组,也就是说定义了N个不同指向 int 型的指针。
1
2 int a, b, c, d;
int *p[] = {&a, &b, &c, &d};
int (*p)[N]
表示定义一个数组指针,指向一个 int [N] 型数组的指针。
1
2
3 int a[2][3] = {1, 2, 3, 4, 5, 6};
int (*p)[3] = a;
printf("(*(p+1))[1] = a[1][1] = %d\n", (*(p+1))[1]);
int **p
表示定义一个指向指针的指针。
3、指针的值(指针所指向的内存区或地址)
指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为 sizeof(指针所指向的类型)
的一片内存区。
1 |
|
二、运算符&和*
& 是取地址运算符,* 是间接运算符。
&a 的运算结果是一个指针,该指针的值是声明 a 时开辟的地址,指针的类型是 a 的类型对应的指针类型,指针所指向的类型就是 a 的类型,指针所指向的地址就是 a 的地址。
*p 的运算结果是 p 所指向的东西,类型是 p 指向的类型,所占用的地址是 p 所指向的地址。
1 |
|
三、数组与指针
重点补充:
- 指针的加减,以指针所指向的类型为单位进行偏移。
- 首地址:一段内存空间中的第一个存储单元的地址,其类型为该存储单元类型对应的指针类型。
1、一维数组与指针
1 |
|
2、二维数组与指针
1 |
|
对于三维数组 int a[1][2][3],有:
- 定位到数组元素 a[x][y][z] :
*(*(*(a+x)+y)+z)
- 指针(数组名)与自身类型、指向类型以及单位偏移量的关系表
指针或数组名 | 自身类型 | 指向类型 | 单位偏移量 |
---|---|---|---|
&a | int(*)[1][2][3] | int[1][2][3] | sizeof(int[1][2][3]) $ = 1×2×3×4 = 24$字节 |
a | int(*)[2][3] | int[2][3] | sizeof(int[2][3]) $ = 2×3×4 = 24$字节 |
a[0] | int(*)[3] | int[3] | sizeof(int[3]) $ = 3×4 = 12$字节 |
a[0][0] | int* | int | sizeof(int) $= 4$字节 |
总结:
- 一维数组的首地址是该一维数组的第一个数组元素的地址;二维数组的首地址是该二维数组的第一个一维数组的地址。
- a 和 &a 的结果都是数组的首地址,但他们的类型是不一样的:a—— 数据类型 *;&a—— 数据类型 (*) [数组元素个数]。
- 数组名仅仅是“相当于”指针,而并不是真的是指针,数组名仅仅只是一个常量(一个值为数组首元素地址的常量),所以不能进行加减运算,而常量更是无法取地址的,之所以有 &a,是因为这里的 a 此时代表了整个数组。
四、结构体与指针
访问结构体内的元素有两种方法:“ .
”操作和“ ->
”操作。现将 studentInfo 型定义为如下形式:
1 | struct studentInfo { |
此时结构体变量中定义了普通变量 stu
和指针变量 p
。
于是访问 stu
中变量的写法如下:
1 | stu.id |
而访问指针变量 p
中元素的写法有两种,如下所示:
1 | (*p).id 或 p -> id |
五、函数与指针
指针类型可以作为函数参数的类型,这时视为把变量的地址传入函数。如果在函数中对这个地址中的元素进行改变,原先的数据就会确实地被改变,这种传递方式被称为地址传递。
由于函数在接收参数的过程中是单向一次性的值传递,即将实参传输给对应的形参,这样相当于产生了一个副本,对这个副本的操作不会影响实参的值,只有在获取实参地址的情况下对形参进行操作,才能真正地修改实参。
1 |
|